<?php
/**
 * BaseModel
 * @copyright (c) 2011-2020, Hangzhou DOJI Tech Co., Ltd.
 * This is NOT a freeware, use is subject to license terms.
 * @package BaseRepository.php
 * @link https://www.tingjunapp.com/
 * @author wujun@lanqb.cn
 * @: BaseModel.php 310000 2020-11-06 15:07:29 $
 * */

namespace App\Models;

use App\Common\Libs\Query\SubQuery;
use App\Consts\ExceptionCode;
use App\Consts\GlobalConst;
use App\Consts\GlobalResultCode;
use App\Exceptions\Error;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;

/**
 * Class BaseModel
 *
 * @package App\Models
 * @method static \Illuminate\Database\Eloquent\Builder select($columns = ['*'])
 * @method static \Illuminate\Database\Eloquent\Builder where($column, $operator = null, $value = null, $boolean = 'and')
 * @method static \Illuminate\Database\Eloquent\Builder|BaseModel newModelQuery()
 * @method static \Illuminate\Database\Eloquent\Builder|BaseModel newQuery()
 * @method static \Illuminate\Database\Eloquent\Builder|BaseModel getQuery()
 * @mixin \Eloquent
 */
class BaseModel extends Model
{
    use HasFactory, SubQuery;

    /** @var $instance */
    protected static $instances;

    /** @var $statics */
    protected static $statics;

    /** @var array $fields */
    public $fields = [];

    /** @var string $dateFormat */
    protected $dateFormat = 'U';

    /** @var array $guarded */
    protected $guarded = [];

    /**
     * set failed format
     * @example ['updated_at' => 'timestamp', 'created_at' => 'timestamp']
     * @preview integer | real | float | double | decimal:<digits> | string | boolean | object | array | collection | timestamp | date | datetime
     * @var array $casts
     */
    protected $casts = [];

    /**
     * get builder instance
     * [!] 如果有分表，id为必传
     * @param string $id table primary key
     * @param bool $attach add id condition
     * @return \Illuminate\Database\Eloquent\Builder|static|mixed
     */
    public static function getInstance($id = '', $attach = false)
    {
        if (self::isSubTable() && empty($id)) {
            return Error::handle('', ExceptionCode::LOGIN_FAIL, GlobalResultCode::SUB_TABLE__ID_EMPTY, true);
        }
        if (!empty($id)) {
            $query = self::setTableNameById($id, $attach);
        } else {
            $query = self::query();
        }
        return $query;
    }

    /**
     * get static model
     * [!] 如果有分表，id为必传
     * @param string $id
     * @param bool $force
     * @return static|\Illuminate\Database\Eloquent\Builder|Model|mixed
     */
    public static function getModel($id = '', $force = false)
    {
        $index = $id ?: 'default';
        if (!$force && isset(self::$statics[static::class][$index])) {
            return self::$statics[static::class][$index];
        }
        $model = new static;
        if (empty($id) && $force) {
            return self::$statics[static::class][$index] = $model;
        }
        if (self::isSubTable() && empty($id) && empty($force)) {
            return Error::handle('', ExceptionCode::LOGIN_FAIL, GlobalResultCode::SUB_TABLE__ID_EMPTY, true);
        }
        if (!empty($id)) {
            $model = $model->setTable($model->table . self::getTableId($id));
        }
        return self::$statics[static::class][$index] = $model;
    }

    /**
     * get table name by id
     * [!] 计算 $id 的32位 CRC（循环冗余校验）值，得到真实分表名
     * @param string|integer $id primary key
     * @return string
     */
    public static function getTableNameById($id)
    {
        $model = self::getModel($id, true);
        return $model->getTable();
    }

    /**
     * 获取分表ID
     * @param $id
     * @return int
     */
    public static function getTableId($id)
    {
        $tableCount = GlobalConst::DATABASE_TABLE_NUM;
        return sprintf('%u', crc32($id)) % $tableCount;
    }

    /**
     * set table name by id
     * [!] 根据 $id 值重新更新表名为分表名
     * @param string|integer $id primary key
     * @param bool $attach add id condition
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public static function setTableNameById($id, $attach = false)
    {
        $model = self::getModel();
        $tableName = self::getTableNameById($id);
        $model = $model->setTable($tableName);
        $attach && $model = $model->where($model->primaryKey, $id);
        return $model;
    }

    /**
     * get table name
     * @param mixed $id primary key
     * @return string
     */
    public static function tableName($id = '')
    {
        $model = self::getModel();
        if ($id) {
            return self::getTableNameById($id);
        }
        return $model->getTable();
    }

    /**
     * set table name
     * @param $tableName
     * @return \Illuminate\Database\Eloquent\Builder|Model|static
     */
    public static function setTableName($tableName)
    {
        $model = self::getModel('', true);
        $model = $model->setTable($tableName);
        return $model;
    }

    /**
     * set table name by table count
     * @param $tableCount
     * @return \Illuminate\Database\Eloquent\Builder|Model|static
     */
    public static function setTableNameByTableCount($tableCount)
    {
        $tableCount = strtolower($tableCount);
        $tbName = self::getTableNameByTableIndex($tableCount);
        return self::setTableName($tbName);
    }

    /**
     * get model base table name
     * @return string
     */
    public static function getBaseTableName()
    {
        $model = self::getModel('', true);
        return $model->getTable();
    }

    /**
     * get table name by index
     * @param $index 0 | 1 | 2 | 3
     * @return string
     */
    public static function getTableNameByTableIndex($index)
    {
        return self::getBaseTableName() . $index;
    }

    /**
     * 按季度划分表名
     * @param $time
     * @return string
     */
    public static function getTableNameBySeason($time = 0)
    {
        $time = $time ?: time();
        $year = date('Y', $time);
        $season = ceil((date('n', $time)) / 3);
        $season = str_pad($season, 2, '0', STR_PAD_LEFT);
        $index = $year . $season;
        return self::getBaseTableName() . $index;
    }

    /**
     * 按月份划分表名
     * @param $time
     * @return string
     */
    public static function getTableNameByMonth($time = 0)
    {
        $time = $time ?: time();
        $year = date('Y', $time);
        $month = date('m', $time);
        $month = str_pad($month, 2, '0', STR_PAD_LEFT);
        $index = $year . $month;
        return self::getBaseTableName() . $index;
    }

    /**
     * Begin querying the model.
     * @param string $id
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public static function query($id = '')
    {
        $model = self::getModel($id);
        return $model->newQuery();
    }

    /**
     * 判断是否为分表
     * @return false|string
     */
    public static function isSubTable()
    {
        $table = self::getBaseTableName();
        return substr($table, -1) == '_';
    }


    /**
     * 获取分表模型下所有联合的子查询
     * [!] 默认使用 union all 快速查询，注意处理重复记录
     * @param array|mixed $where
     * @param array $field
     * @param bool $unionAll
     * @param array $idAry
     * @return \Illuminate\Database\Eloquent\Builder|Model|mixed|static
     */
    public static function getSubModel($where, $field = ['*'], $unionAll = true, $idAry = [])
    {
        $idAry = $idAry ? : GlobalConst::DATABASE_TABLE_INDEX;
        $model = self::getModel('', true);
        $baseTableName = $model->getTable();
        $tableCount = count($idAry);
        $model = DB::table($baseTableName . $idAry[0])->select($field)->where($where);
        for ($i = 1; $i < $tableCount; $i++) {
            $subTableName = $baseTableName . $idAry[$i];
            $subQuery = DB::table($subTableName)->select($field)->where($where);
            $model = $model->union($subQuery, $unionAll);
        }
        $subSql = getSql($model);
        return DB::table(DB::raw("({$subSql}) as subTable"));
    }

}
